package jutf8search;

import java.io.IOException;
import java.util.Vector;

/**
 * Tests the functionality of the exact searching. Contains a method of testing all the functions
 * specified by the Search interface. 
 *
 */
public class Test
{
	private int matchNum = 5;
    /**
	 * Main method to test the features implemented in the library.
	 * 
	 * @param args. Usage is either: filename -p pattern distance -run
	 * filename -f file distance -run
	 * filename -p pattern distance -time iterations
	 * filename -f file distance -time iterations
	 */
	public static void main(String[] args)
	{
        Test prog = new Test();
        try
        {
            // print some help
            if(args[0].equals("-help") && args.length==1)
            {
                prog.println("usage:");
                prog.println("<file> <query> <distance> -run");
                prog.println("<file> <query> <distance> -time <iterations>");
                prog.println("0 <= distance < query length");
            }
            else if(args.length >= 4)
            {
                // process first arguments
                String filename = args[0];
                byte[] pattern = null;
                //if(args[1].equals("-p"))
                    pattern = Utilities.getBytes(args[2]);
                //else if(args[1].equals("-f"))
                //    pattern = FileIO.readQuery(args[2]);
                int distance = Integer.parseInt(args[3]);
                // run the tests
                if(args[4].equals("-run") && args.length==5)
                {
                    prog.testExact("BM",filename,pattern);
                    prog.testExact("BMH",filename,pattern);
                    prog.testExact("KMP",filename,pattern);
                    prog.testFuzzy("Counting",filename,pattern,distance);
                    prog.testFuzzy("BitapHamming",filename,pattern,distance);
                }
                // time the methods
                else if(args[4].equals("-time") && args.length==6)
                {
                    int repeats = Integer.parseInt(args[4]);
                    long a, b, c;
                    prog.println("Running exact search algorithms "+repeats+" times");
                    a = prog.timeExact("BM",filename,pattern,repeats); 
                    b = prog.timeExact("BMH",filename,pattern,repeats);
                    c = prog.timeExact("KMP",filename,pattern,repeats);
                    prog.println("average BM       = "+a+"ms");
                    prog.println("average BMH      = "+b+"ms");
                    prog.println("average KMP      = "+c+"ms");
                    prog.println("Running fuzzy search algorithms "+repeats+" times, distance="+distance);
                    a = prog.timeFuzzy("Counting",filename,pattern,distance,repeats);
                    prog.timeFuzzy("BitapHamming",filename,pattern,distance,repeats);
                    prog.println("average Counting = "+a+"ms");
                    System.out.println("average BitapHamming = "+b+"ms");
                }
                else
                    prog.println("Invalid arguments, run -help for usage");
            }
            else
                prog.println("Invalid arguments, run -help for usage");
        }
        catch(Exception e)
        {
            e.printStackTrace();
            System.exit(1);
        }
	}
   
	/**
	 * Returns the average time over repeats trials in ms to execute all ExactSearch methods
	 * @param algo The name of the algorithm to use
	 * @param filename The name of the file to be searched over
	 * @param query The byte[] containing the bytes to search for
	 * @param repeats The number of times to repeat the tests
	 * @return The average time in ms it takes to run the tests.
	 * @throws Exception If there is an error opening the file, or an error building the table.
	 */
    long timeExact(String algo, String filename, byte[] query, int repeats) throws Exception
    {
        ExactSearch s = new ExactSearch(algo);
        long total=0;
        s.buildTable(query);
        for(int i=0; i<repeats; i++)
        {
            long start = System.currentTimeMillis();
            runExactMethods(s, filename, algo);
            long end = System.currentTimeMillis();
            total += end-start;
        }
        return total / (long)repeats; 
    }
 
    /**
     * Times the average time over repeated trials in ms to execute all FuzzySearch Methods.
     * @param algo The name of the algorithm to use
     * @param filename The name of the file to search over
     * @param query The byte[] containing the bytes to be searched for
     * @param distance The Hamming distance to use
     * @param repeats The number of times to repeat the tests
     * @throws IOException If there is an error opening the file.
     * @return The average time in ms.
     */
    long timeFuzzy(String algo, String filename, byte[] query, int distance, int repeats) throws IOException, Exception
    {
        FuzzySearch s = new FuzzySearch(algo);
        long total=0;
        s.preprocess(query);
        for(int i=0; i<repeats; i++)
        {
            long start = System.currentTimeMillis();
            runFuzzyMethods(s,filename,algo,distance);
            long end = System.currentTimeMillis();
            total += end-start;
        }
        return total/(long)repeats;
    }

    /**
     * Tests an ExactSeach methods by calling TestExactMethods then
     * tests the saving and loading of the query.
     * @param algo The algorithm to test.
     * @param filename The file to search.
     * @param query The query to search for.
     */
    void testExact(String algo, String filename, byte[] query) throws IOException, Exception
    {
        ExactSearch s = new ExactSearch(algo);
        s.buildTable(query);
        testExactMethods(s, filename, algo);
        String saveTable = algo+"table.ser";
        s.saveTable(saveTable);
        println("Saved serialised file ("+saveTable+") for query = " + query);
        println("query as utf8 string: " + new String(query, "UTF8"));
        s = new ExactSearch(algo);
        s.loadTable(saveTable);
        if(query.length == s.getPatternSize()) println("Loaded "+saveTable+", query matches");
    }
   
    /**
     * Tests FuzzySearch methods by calling TestFuzzyMethods then
     * tests the saving and loading of the query.
     * @param algo The algorithm to test.
     * @param filename The file to search.
     * @param query The query to search for.
     */
    void testFuzzy(String algo, String filename, byte[] query, int distance) throws IOException, Exception
    {
        FuzzySearch s = new FuzzySearch(algo);
        s.preprocess(query);
        testFuzzyMethods(s, filename, algo, distance);
        String saveTable = algo+"preprocess.ser";
        s.savePreprocess(saveTable);
        println("Saved serialised file ("+saveTable+") for query = " + query);
        s = new FuzzySearch(algo);
        s.loadPreprocess(saveTable);
        if(query.length == s.getPatternSize()) println("Loaded "+saveTable+", query matches");
    }
   
    /**
     * Calls each of the FuzzySearch methods in turn.
     * 
     * @param search The FuzzySearch object to use.
     * @param filename The filename of the file to search over
     * @param algo The name of the algorithm being used
     * @param distance The Hamming distance to use
     * @throws IOException If the file cannot be opened to any reason.
     */
    void runFuzzyMethods(FuzzySearch search, String filename, String algo, int distance) throws IOException
    {
		search.exists(filename,distance);
		search.numMatches(filename,distance);
		search.charPos(filename,distance);
		search.charPos(filename,matchNum,distance);
		search.bytePos(filename,distance);
		search.bytePos(filename,matchNum,distance);
        search.fullSearch(filename,distance);
    }

    /**
     * Tests the FuzzySearch methods.
     * 
     * @param search The FuzzySearch object to use
     * @param filename The filename of the file to search over
     * @param algo The name of the algorithm being used
     * @param distance The Hamming distance to use
     * @throws IOException If the file cannot be opened for any reason
     */
    void testFuzzyMethods(FuzzySearch search, String filename, String algo, int distance) throws IOException
    {
		println("[FUZZY] checking pattern over "+filename+" using "+algo+" with distance="+distance+" returns:");
		println(algo+".exists:     "+search.exists(filename,distance));
		println(algo+".numMatches: "+search.numMatches(filename,distance));
		println(algo+".charPos     "+search.charPos(filename,distance));
		println(algo+".charPos:    "+search.charPos(filename,matchNum,distance)+" (MatchNum="+matchNum+")");
		println(algo+".bytePos:    "+search.bytePos(filename,distance));
		println(algo+".bytePos:    "+search.bytePos(filename,matchNum,distance)+" (MatchNum="+matchNum+")");
		print(algo+".fullSearch: ");
        Vector<Long> results = search.fullSearch(filename,distance);
        for(int i=0; i<results.size(); i++)
          print(i+":"+results.get(i)+" ");
        println("");
    }
	
	/**
	 * Tests the exact searching methods defined by the ExactSearch interface.
	 * @param search The exact search object to test.
	 * @param filename The filename to search over.
	 * @param algo The algorithm being used. Only used for printing to the screen. 
	 */
	void runExactMethods(ExactSearch search, String filename, String algo) throws IOException
	{
		search.exists(filename);
		search.numMatches(filename);
		search.charPos(filename);
		search.charPos(filename,matchNum);
		search.bytePos(filename);
		search.bytePos(filename,matchNum);
        search.fullSearch(filename);
	}

	/**
	 * Tests the exact searching methods defined by the ExactSearch interface.
	 * @param search The exact search object to test.
	 * @param filename The filename to search over.
	 * @param algo The algorithm being used. Only used for printing to the screen. 
	 */
	void testExactMethods(ExactSearch search, String filename, String algo) throws IOException
	{
		println("[EXACT] checking pattern over "+filename+" using "+algo+" returns:");
		println(algo+".exists:     "+search.exists(filename));
		println(algo+".numMatches: "+search.numMatches(filename));
		println(algo+".charPos     "+search.charPos(filename));
		println(algo+".charPos:    "+search.charPos(filename,matchNum)+" (MatchNum="+matchNum+")");
		println(algo+".bytePos:    "+search.bytePos(filename));
		println(algo+".bytePos:    "+search.bytePos(filename,matchNum)+" (MatchNum="+matchNum+")");
		print(algo+".fullSearch: ");
        Vector<Long> results = search.fullSearch(filename);
        for(int i=0; i<results.size(); i++)
          print(i+":"+results.get(i)+" ");
        println("");
	}

    private void println(String s) { System.out.println(s); }
    private void print(String s) { System.out.print(s); }
}
